<?php
/**
 * Simple PHP Contact Form
 *
 * This file is part of the Simple PHP Contact Form which is subject to the New
 * BSD License {@see LICENSE} which you would be advised to read before using,
 * modifying or redistributing this software.
 *
 * PHP version 5.2
 *
 * @category  Simple-PHP-Contact-Form
 * @package   Application_Library
 * @author    jah <jah@jahboite.co.uk>
 * @copyright 2010 jah <jah@jahboite.co.uk>
 * @license   New BSD License {@see LICENSE}
 * @version   SVN: $Id$
 * @link      http://code.google.com/p/simple-php-contact-form/
 */


/**
 * Validates an input string against a list of valid recipient identifiers.
 * In order to successfully validate: $recipients must be set with an
 * associative array where the array keys are the valid recipients against which
 * to check; there must be at least one member in the $recipients array; the
 * input to be validated must be a string and must not contain double or mixed
 * encoding and the input string must exactly match one of the array keys.
 *
 * @category  Simple-PHP-Contact-Form
 * @package   Application_Library
 * @author    jah <jah@jahboite.co.uk>
 * @copyright 2010 jah <jah@jahboite.co.uk>
 * @license   New BSD License {@see LICENSE}
 * @version   Release: @package_version@
 * @link      http://code.google.com/p/simple-php-contact-form/
 */
class Custom_Validate_ValidRecipient extends Zend_Validate_Abstract
{
    /**
     * A Zend_Config object that will behave like array where the array keys are
     * recipient names.  The array values are not examined.
     *
     * @var array
     */
    private $_recipients      = null;
    private $_dummyRecipients = array();


    const INVALID           = 'INVALID';
    const INVALID_RECIPIENT = 'INVALID_RECIPIENT';
    const DUMMY_RECIPIENT   = 'DUMMY_RECIPIENT';

    protected $_messageTemplates = array(
        self::DUMMY_RECIPIENT
            => 'Please choose who you would like to contact.',
        self::INVALID_RECIPIENT
            => 'Input is not a valid choice of recipient.',
        self::INVALID
            => 'Input must be a string.',
    );


    /**
     * Sets Validator Options which consist of an associative array where the
     * valid recipient identifiers (e.g. name) are the array keys.
     *
     * @param string|array|Zend_Config $options The recipients configuration.
     *
     * @return null
     */
    public function __construct($options = array())
    {
        if (!is_array($options)) {

            $options = func_get_args();
            if (!empty($options)) {
                $temp['recipients'] = array_shift($options);
            }
            if (!empty($options)) {
                $temp['dummy'] = array_shift($options);
            }
            $options = $temp;
        }

        if (array_key_exists('recipients', $options)) {
            $this->setRecipients($options['recipients']);
        }

        if (array_key_exists('dummy', $options)) {
            $this->setDummyRecipients($options['dummy']);
        }
    }


    /**
     * Validates the input string against a list of valid recipients.
     *
     * @param string $input The input to be validated as a recipient.
     *
     * @return bool True if input string is a valid recipient, otherwise
     *              False.
     */
    public function isValid($input)
    {
        $auditor  = ESAPI::getAuditor('App_Validate_Recipient');

        if (!is_string($input)) {
            $auditor->warning(
                Auditor::SECURITY, false,
                'isValid expects a string!'
            );
            $this->_error(self::INVALID);
            return false;
        }
        if ($this->_recipients instanceof Zend_Config !== true) {
            $this->_error(self::INVALID_RECIPIENT);
            $auditor->warning(
                Auditor::SECURITY, false,
                'isValid requires an array of recipients!'
            );
            return false;
        }

        $encoder = ESAPI::getEncoder();

        // canonicalise the input string.
        $canonical = null;
        try {
            $canonical = $encoder->canonicalize($input, true);
        } catch (Exception $e) {
            // Process the input no further.
            $this->_error(self::INVALID_RECIPIENT);
            $auditor->warning(
                Auditor::SECURITY, false,
                'isValid rejected a string in which double or mixed encoding was detected.',
                $e
            );
            return false;
        }
        // Convert input to lower case
        $charEnc = mb_detect_encoding($canonical);
        $canonicalLower = mb_strtolower($canonical, $charEnc);

        // Get a whitespace removal filter
        $whitespace = new Zend_Filter_PregReplace(
            array('match' => '/ /', 'replace' => '')
        );

        // for each of our valid recipients use an identical validator
        // to determine whether $canonical matches.
        $validator = new Zend_Validate_Identical();
        foreach ($this->_recipients as $_ => $cfg) {

            foreach ($cfg as $key => $validRecipient) {

                if ($key !== 'display') {
                    continue;
                }
                $charEnc = mb_detect_encoding($validRecipient . '');
                $validRecipientL = mb_strtolower($validRecipient, $charEnc);
                $validRecipientS = $whitespace->filter($validRecipientL);

                $validator->setToken($validRecipientL);
                if ($validator->isValid($canonicalLower)) {
                    return true;
                }

                $validator->setToken($validRecipientS);
                if ($validator->isValid($canonicalLower)) {
                    return true;
                }
            }
        }

        // if that fails, the form has been tampered with or a dummy option has
        // been selected - check for the latter of these now:
        foreach ($this->_dummyRecipients as $dummy => $value) {

            $charEnc = mb_detect_encoding($dummy . '');
            $dummyL = mb_strtolower($dummy, $charEnc);
            $dummyS = $whitespace->filter($dummyL);

            $validator->setToken($dummyL);
            if ($validator->isValid($canonicalLower)) {
                $this->_error(self::DUMMY_RECIPIENT);
                return false;
            }

            $validator->setToken($dummyS);
            if ($validator->isValid($canonicalLower)) {
                $this->_error(self::DUMMY_RECIPIENT);
                return false;
            }
        }

        $auditor->warning(
            Auditor::SECURITY, false,
            "isValid. Input [{$canonicalLower}] is not a valid recipient."
        );
        $this->_error(self::INVALID_RECIPIENT);
        return false;
    }


    /**
     * Sets $recipients to the supplied Zend_Config object.  If the parameter is
     * an array with a key named 'recipients' and a value which is instance of
     * Zend_Config then that array value will be assigned.  Non-Zend_config
     * paremeters will otherwise be ignored.
     *
     * @param array|Zend_Config $recipients The recipients configuration.
     *
     * @return null
     */
    public function setRecipients($recipients)
    {
        if (   is_array($recipients)
            && array_key_exists('recipients', $recipients)
        ) {
            $recipients = $recipients['recipients'];
        }
        if ($recipients instanceof Zend_Config) {
            $this->_recipients = $recipients;
        }
    }


    /**
     * Sets $dummyRecipients to the supplied array.  Dummy recipients are display
     * names visible in the form element which allows recipient selection.  Their
     * purpose is to allow the form element to be displayed with a dummy recipient
     * already selected and force the visitor to select from one of the other
     * options.
     *
     * @param array $dummyRecipients Ideally, a single dummy recipient.
     *
     * @return null
     */
    public function setDummyRecipients($dummyRecipients)
    {
        if (is_array($dummyRecipients)) {
            $this->_dummyRecipients = $dummyRecipients;
        }
    }
}
